package org.fhnw.aigs.server.gui;
import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.Rectangle;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Vector;
import java.util.logging.Logger;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.ListModel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.plaf.metal.MetalBorders;
import org.fhnw.aigs.commons.Game;
import org.fhnw.aigs.commons.Player;
import org.fhnw.aigs.server.common.LogRouter;
import org.fhnw.aigs.server.common.LoggingLevel;
import org.fhnw.aigs.server.communication.ServerCommunication;
import org.fhnw.aigs.server.gameHandling.GameManager;
import org.fhnw.aigs.server.gameHandling.RecompileClassesAction;
import org.fhnw.aigs.server.gameHandling.ReloadClassesAction;
import org.fhnw.aigs.server.common.ServerConfiguration;
import org.fhnw.aigs.server.common.ShowLogsAction;
import org.fhnw.aigs.server.gameHandling.User;
/**
* This is the server's GUI. Several controls and information panels are provided.<br>
* Controls:<br>
* <ul>
* <li>Start server (service)</li>
* <li>Stop server (service)</li>
* <li>Close the program</li>
* <li>Show the logs</li>
* <li>Reload the avilable games</li>
* <li>Recompile and reload the avalibale games. Thos only applies if game projects are stored on the AIGS server</li>
* <li>Call the server settings dialog (configuration)</li>
* <li>Call the user management dialog</li>
* <li>End a game (forced) if selected in the list of waiting or running games</li>
* </ul>
*<br>
* Information panels:<br>
* <ul>
* <li>The server log</li>
* <li>A list of available / installed games</li>
* <li>A list of active users. This list will show the persistent users if anonymous login is disabled, otherwise adHoc created users</li>
* <li>A list of active (running) games</li>
* <li>A list of waiting (not started yet) games</li>
* <li>Detail information panel with a list of participants of a selected game</li>
* </ul>
* <br>
* Please not that some functionality is implemented in subclasses, e.g.
* the server start.<br>
* <br>v1.1 New functions and fixes
* <br>v1.2 New functions (settings and user management) and change from static to singleton
* <br>v1.3 Changing of logging
* @author Matthias Stöckli
* @version v1.3 (Raphael Stoeckli, 24.02.2015)
*/
public class ServerGUI extends JFrame {
/**
* The singleton instance of the GUI
*/
private static ServerGUI instance;
/**
* Gets the singleton instance or initializes it if not defined
* @return instance of the GUI
*/
public static ServerGUI getInstance() {
if (instance == null)
{
instance = new ServerGUI();
}
return instance;
}
public Game selectedGame;
private JList gameList;
private JScrollPane gameListScrollPane;
private JList waitingGameList;
private JScrollPane waitingGameListScrollPane;
private JList availableGamesList;
private JScrollPane availableGamesListScrollPane;
private JTextArea logTextArea;
private JScrollPane logTextAreaScrollPane;
private JPanel gameInformationPanel;
private JPanel topPanel;
private JPanel statusPanel;
private JPanel settingsPanel;
private JPanel buttonPanel;
private Vector<Game> listContent;
private Vector<Game> waitingListContent;
private Vector<String> availableGamesContent;
private Vector<User> activeUsersContent;
private JButton startButton;
private JButton stopButton;
private JButton closeButton;
private JButton showLogsButton;
private JButton reloadClassesButton;
private JButton recompileClassesButton;
private JButton settingsButton;
private JButton userManagementButton;
private JLabel statusLabel;
private JLabel gameNameLabel;
private JLabel participantsLabel;
private JLabel versionLabel;
private JList activeUsersList;
private JScrollPane activeUsersListScrollPane;
private JList participantsList;
private JScrollPane particiantsScrollPane;
private ListModel participantsModel;
private JLabel partyLabel;
private JLabel publicGameLabel;
private JLabel idLabel;
private JLabel ipLabel;
private JButton endGameButton;
private LimitLinesDocumentListener logListener;
public LimitLinesDocumentListener getLogListener() {
return logListener;
}
public void setLogListener(LimitLinesDocumentListener logListener) {
this.logListener = logListener;
}
/**
* Constructor of GUI class
*/
public ServerGUI() {
super("AIGS - AI Game Server ");
setLookAndFeel();
setUpWindow();
setAIGSTrayIcon();
setUpTopPanel();
JPanel middlePanel = new JPanel(null);
JPanel allGamesPanel = new JPanel(new GridLayout(2, 1));
setUpLoggingPanel();
setUpGameInformationPanel();
setUpGameList();
setUpAvailableGames();
setUpActiveUsers();
loadUsers();
setupActionListeners();
logTextAreaScrollPane.setBounds(10,0,650,615);
gameInformationPanel.setBounds(1020,200,220,415);
allGamesPanel.setBounds(670,200,340, 415);
availableGamesListScrollPane.setBounds(670, 0, 280, 190); //570>280
activeUsersListScrollPane.setBounds(960, 0, 280, 190); //570>285
allGamesPanel.add(gameListScrollPane);
allGamesPanel.add(waitingGameListScrollPane);
middlePanel.add(logTextAreaScrollPane);
middlePanel.add(allGamesPanel);
middlePanel.add(gameInformationPanel);
middlePanel.add(availableGamesListScrollPane);
middlePanel.add(activeUsersListScrollPane);
this.add(middlePanel, BorderLayout.CENTER);
pack();
}
/**
* Shows the GUI when hidden.
*/
public void showGUI() {
this.setVisible(true);
}
/**
* Sets a Sys-Tray icon for the AIGS. This method is based on the Javadoc
* entry on "SystemTray".
* See: http://docs.oracle.com/javase/7/docs/api/java/awt/SystemTray.html
*/
public void setAIGSTrayIcon() {
// Add an application icon
ImageIcon logoImage = new ImageIcon(getClass().getResource("/imgs/logo24px.png"));
this.setIconImage(logoImage.getImage());
// Set tray icon
TrayIcon trayIcon = null;
if (SystemTray.isSupported()) {
if(ServerConfiguration.getInstance().getHidesOnClose() == true){
this.setDefaultCloseOperation(HIDE_ON_CLOSE);
}else{
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
final JFrame thisFrame = this;
this.addWindowStateListener(new WindowAdapter() {
@Override
public void windowIconified(WindowEvent e) {
thisFrame.setVisible(false);
}
});
}
// Get the SystemTray instance and load an image
SystemTray tray = SystemTray.getSystemTray();
// Listener to open the application.
ActionListener openListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
showGUI();
}
};
// Listener to close the application.
ActionListener closeListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
};
// Create a popup menu
PopupMenu popup = new PopupMenu();
// Creation of the "Öffnen" menu entry
MenuItem defaultItem = new MenuItem("Open AIGS");
defaultItem.addActionListener(openListener);
// Creation of the "Beenden" menu entry
MenuItem closeItem = new MenuItem("Close AIGS");
closeItem.addActionListener(closeListener);
popup.add(defaultItem);
popup.add(closeItem);
trayIcon = new TrayIcon(logoImage.getImage(), "AIGS", popup);
trayIcon.setImageAutoSize(true);
trayIcon.addActionListener(openListener);
// add the tray image
try {
tray.add(trayIcon);
} catch (AWTException e) {
}
}else{
// If tray is not supported close the application.
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
}
}
/**
* Adds the specified game to the game list.
*
* @param game The game to be added
* @param waiting If true, the game will be added to the waiting games list, otherwiese to the active games list
*/
public void addGameToList(Game game, boolean waiting) {
if (waiting == true)
{
waitingListContent.add(game);
waitingGameList.setListData(waitingListContent);
}
else
{
listContent.add(game);
gameList.setListData(listContent);
}
}
/**
* Removes the specified game from the game list.
*
* @param game The game to be removed
* @param waiting If true, the game will be removed from the waiting games list, otherwiese from the active games list
*/
public void removeGameFromList(Game game, boolean waiting) {
if (waiting == true)
{
waitingListContent.remove(game);
waitingGameList.setListData(waitingListContent);
waitingGameList.setSelectedIndex(-1);
}
else
{
listContent.remove(game);
gameList.setListData(listContent);
gameList.setSelectedIndex(-1);
}
}
/**
* Adds the specified user to the list
* @param user User to add
*/
public void addUserToList(User user)
{
activeUsersContent.add(user);
activeUsersList.setListData(activeUsersContent);
}
/**
* Removes all usrest from the list
*/
public void removeAllUsersFromList()
{
activeUsersContent.clear();
activeUsersList.setListData(activeUsersContent);
}
/**
* Removes the specified user from the list
* @param user User to remove
*/
public void removeUserFromList(User user)
{
activeUsersContent.remove(user);
activeUsersList.setListData(activeUsersContent);
activeUsersList.setSelectedIndex(-1);
}
/**
* Returns the game having a specified index in the game list.
*
* @param listIndex The position/index in the list.
* @param waiting If true, the source ist the list of waiting games, otherwise the list of active games
* @return Game at the specified position/index.
*/
public Game getGameFromListByListIndex(int listIndex, boolean waiting) {
if (waiting == true)
{
if(listIndex >= 0){
return ServerGUI.getInstance().waitingListContent.elementAt(listIndex);
}else{
return null;
}
}
else
{
if(listIndex >= 0){
return ServerGUI.getInstance().listContent.elementAt(listIndex);
}else{
return null;
}
}
}
/**
* Sets the Look and Feel of the application. The standard LAF for this
* application is <b>Nimbus</b>.
*/
private void setLookAndFeel() {
try {
UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
//LOG//Logger.getLogger(ServerGUI.class.getName()).log(Level.SEVERE, null, ex);
LogRouter.log(ServerGUI.class.getName(), LoggingLevel.severe, null, ex);
}
}
/**
* In this method all necessary steps are taken to set up the upper part of
* the GUI: Butttons and labels are added.
*/
private void setUpTopPanel() {
topPanel = new JPanel(new GridLayout(2, 1));
topPanel.setBorder( new EmptyBorder( 10, 10, 10, 10 ) );
statusPanel = new JPanel(new GridLayout(1,4));
buttonPanel = new JPanel(new GridLayout(1,5));
settingsPanel = new JPanel(new GridLayout(1,2));
settingsPanel.setBorder( new EmptyBorder( 10, 200, 10, 0 ));
this.add(topPanel, BorderLayout.NORTH);
ImageIcon logoImage = new ImageIcon(getClass().getResource("/imgs/logo.png"));
ImageIcon settingsLogo = new ImageIcon(getClass().getResource("/imgs/settings.png"));
ImageIcon usersLogo = new ImageIcon(getClass().getResource("/imgs/users.png"));
JLabel logoLabel = new JLabel(logoImage);
statusLabel = new JLabel("offline");
statusLabel.setFont(new Font("Monospaced", Font.BOLD, 36));
statusLabel.setForeground(new Color(156, 25, 25));
ipLabel = new JLabel(ServerCommunication.getExternalIp());
ipLabel.setFont(new Font("Monospaced", Font.BOLD, 36));
ipLabel.setForeground(Color.GRAY);
startButton = new JButton("Start AIGS");
stopButton = new JButton("Stop AIGS");
stopButton.setEnabled(false);
stopButton.addActionListener(new StopServerAction(statusLabel, startButton, stopButton));
startButton.addActionListener(new StartServerAction(statusLabel, startButton, stopButton));
statusLabel.setForeground(new Color(24, 105, 36));
settingsButton = new JButton(settingsLogo);
settingsButton.addActionListener(new SettingsAction());
settingsButton.setToolTipText("Opens the system settings window");
userManagementButton = new JButton(usersLogo);
userManagementButton.addActionListener(new UserSettingsAction());
userManagementButton.setToolTipText("Opens the user management window");
settingsPanel.add(settingsButton);
settingsPanel.add(userManagementButton);
closeButton = new JButton("Close AIGS");
closeButton.addActionListener(new ServerGUI.CloseServerAction());
showLogsButton = new JButton("Show Logs");
showLogsButton.addActionListener(new ShowLogsAction());
reloadClassesButton = new JButton("Reload classes");
recompileClassesButton = new JButton("Recompile and load classes");
statusPanel.add(logoLabel);
statusPanel.add(statusLabel);
statusPanel.add(ipLabel);
statusPanel.add(settingsPanel);
buttonPanel.add(startButton);
buttonPanel.add(stopButton);
buttonPanel.add(closeButton);
buttonPanel.add(showLogsButton);
buttonPanel.add(reloadClassesButton);
buttonPanel.add(recompileClassesButton);
topPanel.add(statusPanel);
topPanel.add(buttonPanel);
}
/**
* Sets up the window (size, minimum size, layout, 'resizability')
*/
private void setUpWindow() {
this.setSize(1280, 768);
this.setMinimumSize(new Dimension(1280, 768));
this.setLayout(new BorderLayout());
this.setResizable(false);
}
/**
* Sets up the game information panel on the right.
*/
private void setUpGameInformationPanel() {
gameInformationPanel = new JPanel(new BorderLayout());
gameInformationPanel.setBorder(BorderFactory.createTitledBorder(new MetalBorders.TextFieldBorder(), "Selected Game"));
JPanel gameInformationButtonPanel = new JPanel(new FlowLayout());
gameInformationPanel.add(gameInformationButtonPanel, BorderLayout.NORTH);
JPanel rightPanelContentPanel = new JPanel();
rightPanelContentPanel.setLayout(null);
idLabel = new JLabel("ID: ");
idLabel.setBounds(new Rectangle(10, 0, 400, 20));
statusLabel = new JLabel("Status: ");
statusLabel.setBounds(10,25,400,20);
gameNameLabel = new JLabel("Game: ");
gameNameLabel.setBounds(new Rectangle(10, 50, 400, 20));
versionLabel = new JLabel("Version: ");
versionLabel.setBounds(new Rectangle(10, 75, 400, 20));
partyLabel = new JLabel("Party name: ");
partyLabel.setBounds(new Rectangle(10, 100, 400, 20));
publicGameLabel = new JLabel("Public party: ");
publicGameLabel.setBounds(new Rectangle(10, 125, 400, 20));
participantsLabel = new JLabel("Participants:");
participantsLabel.setBounds(new Rectangle(10, 150, 400, 20));
participantsList = new JList();
participantsModel = new DefaultListModel();
participantsList.setModel(participantsModel);
particiantsScrollPane = new JScrollPane(participantsList);
particiantsScrollPane.setBounds(10, 170, 190, 165);
endGameButton = new JButton("End game");
endGameButton.setEnabled(false);
endGameButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
GameManager.terminateGame(selectedGame, "Termination requested by administrator.");
}
});
gameInformationButtonPanel.add(endGameButton);
rightPanelContentPanel.add(idLabel);
rightPanelContentPanel.add(statusLabel);
rightPanelContentPanel.add(gameNameLabel);
rightPanelContentPanel.add(versionLabel);
rightPanelContentPanel.add(participantsLabel);
rightPanelContentPanel.add(partyLabel);
rightPanelContentPanel.add(publicGameLabel);
rightPanelContentPanel.add(particiantsScrollPane);
gameInformationPanel.add(rightPanelContentPanel, BorderLayout.CENTER);
}
/**
* Sets up the logging panel on the left.
*/
private void setUpLoggingPanel() {
logTextArea = new JTextArea();
logTextArea.setEditable(false);
logTextArea.setFont(new Font("Dialog", Font.PLAIN, 12));
logTextArea.setWrapStyleWord(true);
this.logListener = new LimitLinesDocumentListener(ServerConfiguration.getInstance().getLinesToLog());
logTextArea.getDocument().addDocumentListener(this.logListener); // Limits the number of lines
logTextAreaScrollPane = new JScrollPane(logTextArea);
logTextAreaScrollPane.setBorder(BorderFactory.createTitledBorder(new MetalBorders.TextFieldBorder(), "Server Log"));
Logger rootLogger = Logger.getLogger("");
rootLogger.addHandler(new LoggerTextAreaHandler(logTextArea));
}
/**
* Sets up the game lists (waiting and running) in the lower middle part.
*/
private void setUpGameList() {
listContent = new Vector<Game>();
waitingListContent = new Vector<Game>();
gameList = new JList(listContent);
waitingGameList = new JList(waitingListContent);
gameListScrollPane = new JScrollPane(gameList);
waitingGameListScrollPane = new JScrollPane(waitingGameList);
gameListScrollPane.setBorder(BorderFactory.createTitledBorder(new MetalBorders.TextFieldBorder(), "Active Games"));
waitingGameListScrollPane.setBorder(BorderFactory.createTitledBorder(new MetalBorders.TextFieldBorder(), "Waiting Games"));
gameList.addListSelectionListener(new ServerGUI.GameListSelectionListener(gameNameLabel, versionLabel, participantsList, idLabel, partyLabel, publicGameLabel,statusLabel, endGameButton, false));
waitingGameList.addListSelectionListener(new ServerGUI.GameListSelectionListener(gameNameLabel, versionLabel, participantsList, idLabel, partyLabel, publicGameLabel, statusLabel, endGameButton, true));
}
/**
* Sets up the list with available (not running) games on top left.
*/
private void setUpAvailableGames()
{
availableGamesContent = new Vector<>();
availableGamesList = new JList(availableGamesContent);
availableGamesListScrollPane = new JScrollPane(availableGamesList);
availableGamesListScrollPane.setBorder(BorderFactory.createTitledBorder(new MetalBorders.TextFieldBorder(), "Available Games"));
}
/**
* Sets up the list of users (n top right.<br>
* If anonymous login is enbaled, only adHoc created users will be shown in this list.<br>
* Otherwise, all persistent users (logged in or not) and doppelgangers (logged in) will be shown.
*/
private void setUpActiveUsers()
{
activeUsersContent = new Vector<>();
activeUsersList = new JList(activeUsersContent);
activeUsersListScrollPane = new JScrollPane(activeUsersList);
activeUsersListScrollPane.setBorder(BorderFactory.createTitledBorder(new MetalBorders.TextFieldBorder(), "Active Users"));
}
/**
* Sets up the ActionListerners for reload and recompile buttons
*/
private void setupActionListeners()
{
reloadClassesButton.addActionListener(new ReloadClassesAction(availableGamesContent, availableGamesList)); // No listener. Only list will be updated
recompileClassesButton.addActionListener(new RecompileClassesAction(availableGamesContent, availableGamesList));
}
/**
* Loads all users to the user list (top right)
*/
private void loadUsers()
{
this.removeAllUsersFromList();
for(int i = 0; i < User.users.size(); i++)
{
if (ServerConfiguration.getInstance().getIsAnonymousLoginAllowed() == true)
{
if (User.users.get(i).isNonPersistentUser() == true)
{
this.addUserToList(User.users.get(i)); // Add only non-persistent users
}
}
else
{
this.addUserToList(User.users.get(i)); // Add all users
}
}
}
/**
* Handles the click on the settings button
*/
private class SettingsAction implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
SettingsWindow sw = new SettingsWindow();
sw.setVisible(true);
loadUsers(); // reload all users after the settings
}
}
/**
* Handles the click on the user management button
*/
private class UserSettingsAction implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
UserSettingsWindow sw = new UserSettingsWindow();
sw.setVisible(true);
}
}
/**
* Handles the click event on the close button
*/
private class CloseServerAction implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
int result = JOptionPane.showConfirmDialog(null,
"Are you sure you want to close the application?",
"Close the application?",
JOptionPane.YES_NO_OPTION);
if (result == JOptionPane.YES_OPTION) {
System.exit(0);
}
}
}
/**
* Handles user interaction in the game list. Shows current information
* about the selected game and allows to use certain actions
*/
private class GameListSelectionListener implements ListSelectionListener {
private final JLabel gameNameLabel;
private final JList participantsList;
private final JLabel idLabel;
private final JLabel partyLabel;
private final JLabel publicGamelabel;
private final JLabel statusLabel;
private final JLabel versionLabel;
private final JButton endGameButton;
private boolean waiting;
private GameListSelectionListener(JLabel gameNameLabel, JLabel versionLabel, JList participantsList, JLabel idLabel, JLabel partyLabel, JLabel publicGameLabel, JLabel statusLabel, JButton endGameButton, boolean waiting) {
this.idLabel = idLabel;
this.versionLabel = versionLabel;
this.gameNameLabel = gameNameLabel;
this.participantsList = participantsList;
this.partyLabel = partyLabel;
this.publicGamelabel = publicGameLabel;
this.statusLabel = statusLabel;
this.endGameButton = endGameButton;
this.waiting = waiting;
}
@Override
public void valueChanged(ListSelectionEvent e) {
int index;
if (waiting == true)
{
index = waitingGameList.getSelectedIndex();
gameList.clearSelection();
waitingGameList.setSelectedIndex(index);
statusLabel.setText("Status: Waiting") ;
}
else
{
index = gameList.getSelectedIndex();
waitingGameList.clearSelection();
gameList.setSelectedIndex(index);
statusLabel.setText("Status: Active") ;
}
ServerGUI.getInstance().selectedGame = ServerGUI.getInstance().getGameFromListByListIndex(index, waiting); // waiting indicates from which list the value is
DefaultListModel model = (DefaultListModel)participantsList.getModel();
model.removeAllElements();
if(index != -1)
{
if (selectedGame.getVersionString() == null)
{
versionLabel.setText("Version: Unversioned");
}
else
{
versionLabel.setText("Version: " + selectedGame.getVersionString());
}
endGameButton.setEnabled(true);
idLabel.setText("ID: " + Long.toString(selectedGame.getId()));
gameNameLabel.setText("Game: " + selectedGame.getGameName());
partyLabel.setText("Party name: " + selectedGame.getPartyName());
publicGameLabel.setText("Public party: " + Boolean.toString(!selectedGame.isPrivateGame()));
for (Player p : selectedGame.getPlayers()) {
model.addElement(p.getName() + " (ID:" + p.getId() +")");
}
//participantsLabel.setText(participants);
}else
{
endGameButton.setEnabled(false);
statusLabel.setText("Status: ") ;
idLabel.setText("ID: " );
gameNameLabel.setText("Game: ");
versionLabel.setText("Version: ");
partyLabel.setText("Party name: ");
publicGameLabel.setText("Public party: ");
}
}
}
}